<?php
/**
 * @package PHPKit.
 * @author: mawenpei
 * @date: 2016/11/28
 * @time: 14:29
 */
namespace HuoKit\HuoServer\Network\Http;

use HuoKit\HuoServer\Core\Command;
use HuoKit\HuoServer\Core\Di;
use swoole_process as SwooleProcess;
use swoole_http_server as SwooleServer;
use swoole_http_request as SwooleHttpRequest;
use swoole_http_response as SwooleHttpResponse;
use HuoKit\HuoServer\Adapter\ThinkPHP\RequestHandler as ThinkPHPRequestHandler;
use HuoKit\HuoServer\Adapter\Laravel\RequestHandler as LaravelRequestHandler;
use HuoKit\HuoServer\Adapter\Php\RequestHandler as PhpRequestHandler;
use HuoKit\HuoServer\Network\Server as BaseServer;
use HuoKit\HuoServer\Core\PidManager;
use HuoKit\HuoServer\Adapter\ThinkPHP\WorkerStart\InitThinkPHP;
use HuoKit\HuoServer\Adapter\Php\WorkerStart\InitPhp;



class HttpServer extends BaseServer
{
    private $swooleServer;

    protected $serverStartItems = [];

    protected $workerStartItems = [];

    protected $workerBootItems = [];

    protected $config;

    protected $pidManager;

    protected $handler;

    protected $logger;

    protected $process_list = [];

    public function __construct(SwooleServer $swooleServer,$config)
    {
        $this->config = $config;
        $this->swooleServer = $swooleServer;
        $this->pidManager = new PidManager($this->config['pid_path']);
    }

    public function setLogger($logger)
    {
        $this->logger = $logger;
        return $this;
    }

    public function start()
    {
        register_shutdown_function([$this,'exceptionHandler']);
        $this->initFrameWork();
        $this->swooleServer->on('start', [$this, 'onStart']);
        $this->swooleServer->on('shutdown', [$this, 'onShutdown']);
        $this->swooleServer->on('managerStart',[$this,'onManagerStart']);
        $this->swooleServer->on('workerStart', [$this, 'onWorkerStart']);
        $this->swooleServer->on('workerStop', [$this, 'onWorkerStop']);
        $this->swooleServer->on('workerError', [$this, 'onWorkerError']);
        $this->swooleServer->on('pipeMessage',[$this,'onPipeMessage']);
        $this->swooleServer->on('request', [$this, 'onRequest']);

        $this->initTaskEventCallback();

        $this->initProcess();

        if(isset($this->config['setting'])){
            $this->swooleServer->set($this->config['setting']);
        }

        $this->bootServerStartItem();

        $this->swooleServer->start();
    }

    public function onManagerStart(SwooleServer $swooleServer)
    {
        if(isset($this->config['manager_process_name'])) {
            swoole_set_process_name($this->config['manager_process_name']);
        }
    }

    public function onStart(SwooleServer $swooleServer)
    {
        $this->pidManager->saveMasterPid($swooleServer->master_pid);
        if(isset($this->config['process_name'])) {
            swoole_set_process_name($this->config['process_name']);
        }
    }

    public function onShutdown(SwooleServer $swooleServer)
    {
    }

    public function onWorkerStart(SwooleServer $swooleServer,$worker_id)
    {
        if(isset($this->config['process_name'])) {
            if($worker_id >= $swooleServer->setting['worker_num']){
                swoole_set_process_name($this->config['process_name'] . '-task-' . $worker_id);
            }else {
                swoole_set_process_name($this->config['process_name'] . '-worker-' . $worker_id);
            }
        }
        $this->bootWorkerStartItem($worker_id);
    }

    public function onWorkerStop(SwooleServer $swooleServer,$worker_id)
    {
    }

    public function onWorkerError(SwooleServer $swooleServer,$worker_id,$worker_pid,$exit_code)
    {
        //$this->logger->error($exit_code . ':' .swoole_strerror($exit_code));
    }

    protected function initFrameWork()
    {
        if(isset($this->config['framework_adapter'])){
            $handler = null;
            switch($this->config['framework_adapter']){
                case 'thinkphp':
                    $this->handler = new ThinkPHPRequestHandler();
                    $customServerStartItems = isset($this->config['thinkphp']['server_start_items']) ? $this->config['thinkphp']['server_start_items']: [];
                    $this->serverStartItems = array_merge($this->serverStartItems,[],$customServerStartItems);
                    $customWorkerStartItems = isset($this->config['thinkphp']['worker_start_items']) ? $this->config['thinkphp']['worker_start_items']: [];
                    $this->workerStartItems = array_merge($this->workerStartItems,[
                        InitThinkPHP::class
                    ],$customWorkerStartItems);
                    break;
                case 'laravel':
                    $this->handler = new LaravelRequestHandler();
                    break;
                case 'php':
                    $this->handler = new PhpRequestHandler();
                    $customServerStartItems = isset($this->config['php']['server_start_items']) ? $this->config['php']['server_start_items']: [];
                    $this->serverStartItems = array_merge($this->serverStartItems,[],$customServerStartItems);
                    $customWorkerStartItems = isset($this->config['php']['worker_start_items']) ? $this->config['php']['worker_start_items']: [];
                    $this->workerStartItems = array_merge($this->workerStartItems,[
                        InitPhp::class
                    ],$customWorkerStartItems);
                    break;
                default:
                    break;
            }
            //$this->logger->debug('init handler success');
            foreach($this->workerStartItems as $bootsrap){
                $this->workerBootItems[] = Di::make($bootsrap);
            }
        }
    }

    public function onRequest(SwooleHttpRequest $swooleHttpRequest, SwooleHttpResponse $swooleHttpResponse)
    {
        if($this->handler){
            $this->handler->handle($swooleHttpRequest,$swooleHttpResponse,$this);
        }else{
            $swooleHttpResponse->end('handler is not exists');
        }
    }

    public function bootServerStartItem()
    {
        foreach($this->serverStartItems as $bootstrap){
            Di::make($bootstrap)->boot($this->config);
        }
    }

    public function bootWorkerStartItem($worker_id)
    {
        foreach($this->workerBootItems as $bootstrap){
            $bootstrap->boot($this->config,$worker_id);
        }
    }

    public function initProcess()
    {
        if(isset($this->config['process_list'])){
            foreach($this->config['process_list'] as $name=>$data){
                $className = $data['class'];
                $handler = new $className();
                if(is_subclass_of($handler,'\\HuoKit\\HuoServer\\Core\\Process')) {
                    $handler->name = $name;
                    $handler->params = $data['params'];
                    $handler->logger = $this->logger;
                    $process = new SwooleProcess([$handler, 'init'], true, 2);
                    $this->swooleServer->addProcess($process);
                    $handler->process = $process;
                    $this->process_list[$name] = $handler;
                }
            }
        }
    }

    public function initTaskEventCallback()
    {
        if(isset($this->config['setting']['task_worker_num'])){
            $this->swooleServer->on('task',[$this,'onTask']);
            $this->swooleServer->on('finish',[$this,'onFinish']);
        }
    }

    public function onTask($swooleServer,$task_id,$from_id,$data)
    {

    }

    public function onFinish($swooleServer,$task_id,$data)
    {

    }

    public function onPipeMessage($swooleServer,$from_worker_id,$message)
    {

    }

    /**
     * @return SwooleServer
     */
    public function getSwooleServer()
    {
        return $this->swooleServer;
    }

    /**
     * @return array
     */
    public function getAllProcess()
    {
        return $this->process_list;
    }

    public function sendCommand($name,$command,$data)
    {
        $cmd = new Command($command,$data);
        $allProcess = $this->getAllProcess();
        foreach($allProcess as $process_name=>$handler){
            if($name == $process_name){
                $handler->process->write($cmd);
                break;
            }
        }
    }

    public function sendCommandAll($command,$data)
    {
        $cmd = new Command($command,$data);
        $allProcess = $this->getAllProcess();
        foreach($allProcess as $process_name=>$handler){
            $handler->process->write($cmd);
        }
    }

    /**
     * 获取所有子进程返回的命令执行结果
     * @return array
     */
    public function getCommandAll()
    {

        $processAll = $this->getAllProcess();

        $result = [];
        foreach ($processAll as $name => $process) {
            $command = $process->process->read();
            if (!empty($command)) {
                $command = unserialize($command);
                $result[$name] = $command->getResult();

            }

        }

        return $result;

    }

    public function stop()
    {
    }

    public function restart()
    {
    }

    public function reload()
    {
    }

    public function exceptionHandler()
    {
        $error = error_get_last();
        if (isset($error['type'])) {
            switch ($error['type']) {
                case E_ERROR :
                    $severity = 'ERROR:Fatal run-time errors. Errors that can not be recovered from. Execution of the script is halted';
                    break;
                case E_PARSE :
                    $severity = 'PARSE:Compile-time parse errors. Parse errors should only be generated by the parser';
                    break;
                case E_DEPRECATED:
                    $severity = 'DEPRECATED:Run-time notices. Enable this to receive warnings about code that will not work in future versions';
                    break;
                case E_CORE_ERROR :
                    $severity = 'CORE_ERROR :Fatal errors at PHP startup. This is like an E_ERROR in the PHP core';
                    break;
                case E_COMPILE_ERROR :
                    $severity = 'COMPILE ERROR:Fatal compile-time errors. This is like an E_ERROR generated by the Zend Scripting Engine';
                    break;
                default:
                    $severity = 'OTHER ERROR';
                    break;
            }
            $message = $error['message'];
            $file = $error['file'];
            $line = $error['line'];
            $log = "$message ($file:$line)\nStack trace:\n";
            $trace = debug_backtrace();
            foreach ($trace as $i => $t) {
                if (!isset($t['file'])) {
                    $t['file'] = 'unknown';
                }
                if (!isset($t['line'])) {
                    $t['line'] = 0;
                }
                if (!isset($t['function'])) {
                    $t['function'] = 'unknown';
                }
                $log .= "#$i {$t['file']}({$t['line']}): ";
                if (isset($t['object']) && is_object($t['object'])) {
                    $log .= get_class($t['object']) . '->';
                }
                $log .= "{$t['function']}()\n";
            }
            if (isset($_SERVER['REQUEST_URI'])) {
                $log .= '[QUERY] ' . $_SERVER['REQUEST_URI'];
            }
            $this->logger->error($log);
            //$GLOBALS['RESPONSE']->end($log);
        }
    }
}